﻿// -----------------------------------------------------------------------
// This class implements the Hospital class in the specification.
// The basic type HOSPITALID in the specification will be implemented
// as an integer.
//
// Author: Dan Buvid
// Date: April 13, 2012
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace ResidencyPlacement
{
    public class Hospital
    {
        // The state space consists of: 
        // A name
        // An address
        // An ID
        // A number of positions
        // An applied relation
        // An interviewed relation
        // A selected relation
        // A declined relation
        // An offerAccepted relation
        // A ranks sequence
        private string name;
        private string address;
        private int id;
        private int numberOfPositions;
        private Dictionary<Student, Application> applied;
        private Dictionary<Student, Application> interviewed;
        private Dictionary<Student, Application> selected;
        private Dictionary<Student, Application> declined;
        private Dictionary<Student, Application> offerAccepted;
        private List<StudentAndApplication> ranks;

        /// <summary>
        /// This is the constructor that implements the 'Init' state
        /// </summary>
        public Hospital(int id, string name, string address, int numberOfPositions)
        {
            this.id = id;
            this.name = name;
            this.address = address;
            this.numberOfPositions = numberOfPositions;

            applied = new Dictionary<Student, Application>();
            interviewed = new Dictionary<Student, Application>();
            selected = new Dictionary<Student, Application>();
            declined = new Dictionary<Student, Application>();
            offerAccepted = new Dictionary<Student, Application>();
            ranks = new List<StudentAndApplication>();

            if (!stateInvariantCheck())
                Console.WriteLine("\n Init state implemented by the constructor failed because "
                                  + "the state invariant is violated \n");
        }

        /// <summary>
        /// This method implements the state invariant check.
        /// </summary>
        /// <returns>boolean result indicating whether or not state invariant is satisfied.</returns>
        private bool stateInvariantCheck()
        {
            bool result = true;

            // The state invariant asserts that for all students in the
            // domain of applied if their ids match they are the same student
            foreach (var stud1 in applied.Keys)
            {
                foreach (var stud2 in applied.Keys)
                {
                    if (stud1.ID == stud2.ID && stud1 != stud2)
                    {
                        result = false;
                    }
                }
            }

            // The state invariant asserts that the domain of interviewed
            // is a subset of the domain of applied.
            HashSet<Student> appliedDomain = new HashSet<Student>(applied.Keys);
            HashSet<Student> interviewedDomain = new HashSet<Student>(interviewed.Keys);
            if (!interviewedDomain.IsSubsetOf(appliedDomain))
                result = false;

            // The state invariant asserts that the domain of selected
            // is a subset of the domain of interviewed.
            HashSet<Student> selectedDomain = new HashSet<Student>(selected.Keys);
            if(!selectedDomain.IsSubsetOf(interviewedDomain))
                result = false;

            // The state invariant asserts that the union of
            // the domain of declined and offerAccepted is a
            // subset of the domain of selected
            HashSet<Student> declinedDomain = new HashSet<Student>(declined.Keys);
            HashSet<Student> offerAcceptedDomain = new HashSet<Student>(offerAccepted.Keys);
            HashSet<Student> declinedAcceptedDomain = declinedDomain;
            declinedAcceptedDomain.UnionWith(offerAcceptedDomain);    
            if (!declinedAcceptedDomain.IsSubsetOf(selectedDomain))
                result = false;

            // The state invariant asserts that the size of offerAccepted
            // must be less than or equal to numberOfPositions
            if (offerAccepted.Count > numberOfPositions)
                result = false;

            // The state invariant asserts that all StudentAndApplications in ranks
            // are in the domain of selected and the elements in the sequence of applications 
            // in ranks are also present in the range of selected.
            foreach (var studentAndApplication in ranks)
            {
                Application sa = new Application();
                selected.TryGetValue(studentAndApplication.Student, out sa); 

                if(!selectedDomain.Contains(studentAndApplication.Student) 
                    && studentAndApplication.Application == sa)
                    result = false;
            }

            // The state invariant asserts that for all StudentAndApplications in ranks if the student ids
            // of two StudentAndApplications are the same, they are the same StudentAndApplication and
            // ranks is ordered from the highest cumulativeMark to the lowest.
            for (int i = 0; i < ranks.Count; i++)
            {
                for (int j = 0; j < ranks.Count; j++)
                {
                    if (ranks[i].Student.ID == ranks[j].Student.ID && ranks[i].Student != ranks[j].Student)
                        result = false;

                    if (i < j && !(ranks[i].Application.CumulativeMark <= ranks[j].Application.CumulativeMark))
                        result = false;
                }
            }

            return result;
        }

        /// <summary>
        /// This method implements the 'StudentApplies' operation.
        /// </summary>
        /// <param name="who">The student applying</param>
        /// <param name="application">The application for the student</param>
        public void StudentApplies(Student who, Application application)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentApplies", "before");

            // The precondition ensures that 
            // (i) there is not already a student in the domain of applied who
            //     has an id equal to who.id
            // (ii) the status of application is Applied
            HashSet<Student> appliedDomain = new HashSet<Student>(applied.Keys);
            foreach (var s in appliedDomain)
            {
                if (s.ID == who.ID)
                    throw new PreconditionException("Hospital", "StudentApplies", "A student with the provided ID has already applied.");
            }

            if (application.Status != ApplicationStatus.Applied)
                throw new PreconditionException("Hospital", "StudentApplies", "The provided application's status was not Applied.");

            // The postcondition ensures that a relation from 'who' to 
            // 'application' has been made in applied.
            applied.Add(who, application);
            NotifyPropertyChanged("Applied");

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentApplies", "after");
        }

        /// <summary>
        /// This method implements the 'StudentIsInterviewed' operation.
        /// </summary>
        /// <param name="who">The student who is interviewed</param>
        /// <param name="mark">The mark for the interview</param>
        public void StudentIsInterviewed(Student who, int mark)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentIsInterviewed", "before");

            // The precondition ensures that 'who' is a member of the domain of applied
            HashSet<Student> appliedDomain = new HashSet<Student>(applied.Keys);
            if (!appliedDomain.Contains(who))
                throw new PreconditionException("Hospital", "StudentIsInterviewed", 
                    "The student has not yet applied and so cannot be interviewed.");

            // The precondition ensures that 'who' is not a member of the domain of interviewed.
            HashSet<Student> interviewedDomain = new HashSet<Student>(interviewed.Keys);
            if (interviewedDomain.Contains(who))
                throw new PreconditionException("Hospital", "StudentIsInterviewed", 
                    "The student cannot already have interviewed.");

            // The postcondition ensures that the relation from 'who' to 'application'
            // has been added to 'interviewed'.
            Application newApp = new Application();
            newApp.Hospital = id;
            newApp.Status = ApplicationStatus.Interviewed;
            newApp.CumulativeMark = who.EntranceExamMark + mark;
            interviewed.Add(who, newApp);


            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentIsInterviewed", "after");
        }

        /// <summary>
        /// This method implements the 'StudentIsSelected' operation
        /// </summary>
        /// <param name="who">The student who is selected</param>
        public void StudentIsSelected(Student who)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentIsSelected", "before");

            // The precondition ensures that 'who' is a member of the domain of 'interviewed'.
            HashSet<Student> interviewedDomain = new HashSet<Student>(interviewed.Keys);
            if (!interviewedDomain.Contains(who))
                throw new PreconditionException("Hospital", "StudentIsSelected", 
                    "The student must have already interviewed in order to be selected.");

            // The precondition ensures that 'who' is not a member of the domain of 'selected'.
            HashSet<Student> selectedDomain = new HashSet<Student>(selected.Keys);
            if (selectedDomain.Contains(who))
                throw new PreconditionException("Hospital", "StudentIsSelected",
                    "The student cannot already have been selected for a position.");

            // The postcondition ensures that the relation from 'who' to 'application'
            // has been added to 'interviewed'.
            Application newApp = new Application();
            interviewed.TryGetValue(who, out newApp);
            newApp.Status = ApplicationStatus.HasBeenSelected;
            selected.Add(who, newApp);

            // The postcondition ensures that the StudentAndApplication for 'who' and 'application'
            // is inserted in 'ranks' in the correct position.
            StudentAndApplication newStudApp = new StudentAndApplication(who, newApp);
            ranks.Add(newStudApp);
            ranks = ranks.OrderBy(s => s.Application.CumulativeMark).ToList<StudentAndApplication>();

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentIsSelected", "after");
        }

        /// <summary>
        /// This method implements the 'StudentRepliedBackToOffer' operation
        /// </summary>
        /// <param name="who">The student who replied to the offer</param>
        /// <param name="reply">The student's response (true == yes, false == no)</param>
        public void StudentRepliedBackToOffer(Student who, bool reply)
        {
            // The state invariant must be checked before the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentRepliedBackToOffer", "before");

            if (!selected.Keys.Contains(who))
            {
                throw new PreconditionException("Hospital", "StudentRepliedBackToOffer",
                    "The student cannot reply to an offer that was not recieved!");
            }

            if (reply)
            {
                Application newApp = new Application();
                selected.TryGetValue(who, out newApp);
                newApp.Status = ApplicationStatus.OfferAccepted;
                offerAccepted.Add(who, newApp);
            }
            else
            {
                Application newApp = new Application();
                selected.TryGetValue(who, out newApp);
                newApp.Status = ApplicationStatus.Declined;
                declined.Add(who, newApp);
            }

            // The state invariant must be checked after the operation.
            if (!stateInvariantCheck())
                throw new InvariantException("Hospital", "StudentRepliedBackToOffer", "after");
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string p)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(p));
            }
        }

        public int ID
        {
            get
            {
                return this.id;
            }
        }

        public string Name
        {
            get
            {
                return this.name;
            }
        }

        public string Address
        {
            get
            {
                return this.address;
            }
        }

        public int NumberOfPositions
        {
            get
            {
                return this.numberOfPositions;
            }
        }

        public Dictionary<Student, Application> Applied
        {
            get
            {
                return this.applied;
            }
        }

        public Dictionary<Student, Application> Interviewed
        {
            get
            {
                return this.interviewed;
            }
        }

        public Dictionary<Student, Application> Selected
        {
            get
            {
                return this.selected;
            }
        }

        public Dictionary<Student, Application> Declined
        {
            get
            {
                return this.declined;
            }
        }

        public Dictionary<Student, Application> OfferAccepted
        {
            get
            {
                return this.offerAccepted;
            }
        }

        public List<StudentAndApplication> Ranks
        {
            get
            {
                return this.ranks;
            }
        }
    }
}
